package com.wawayaya.bookreader.stroke;

import android.content.ContentResolver;
import android.database.Cursor;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;

import java.io.FileDescriptor;
import java.io.FileInputStream;

/** class to play wave for stroke name only
 * 
 * @author WanKang
 */
public class StrokeWave {
	/// string for log only
	private static final String TAG = StrokeWave.class.getSimpleName();
	/// string of related content provider
	private static final String STROKEPY_CP = "content://com.creative.strokepinyinprovider/pinyin";
	/// buffer size for playing wave
    private int mOutBufSize = 0;
    /// android class for playing wave
	private AudioTrack mOutTrack = null;
	
	/** to play wave, with given rowID in Content Provider
	 * 
	 * @param cr ContentResolver, typical from Activity
	 * @param nStrokeName 1-based rowID
	 * @param bWait to wait till wave-playing finished or not
	 * @return how many millisecond should this thread wait
	 */
	public int PlayWave(ContentResolver cr, int nStrokeName, boolean bWait) {
		if (nStrokeName < 0)
		{
			Log.d(TAG, "unexpected stroke index:" + nStrokeName);
			return 0;
		}
		return PlayWave(cr, null, nStrokeName, bWait);
	}
	/** to play wave by given stroke name/rowID
	 * 
	 * @param cr ContentResolver, typical from Activity
	 * @param strStrokeName string name of stroke, may be null or empty, if rowID is provided
	 * @param nID rowID in Content Provider, ignored, if stroke name is provided
	 * @param bWait to wait till wave-playing finished or not
	 * @return how many 100-millisecond should this thread wait
	 */
	public int PlayWave(ContentResolver cr, String strStrokeName, int nID, boolean bWait) {
		int nMilliSecond = 0;
		try{
			byte[] bData = null;
			if (!TextUtils.isEmpty(strStrokeName))
				bData = loadStrokeWave(cr, strStrokeName);
			else
				bData = loadFileContent(cr, null, nID);
			if (bData==null || bData.length<=44) {
				Log.d(TAG, "data length=" + (bData != null ? bData.length : 0));
				return 0;
			}
			boolean bPlay = PlayData(bData);
			// wait a moment, on zii egg, it is too fast, thread not stopped
			if (bPlay) {
				float fMilliSecond = (bData.length / 2) * 1000 / 44100.0f;
				nMilliSecond = (int)(fMilliSecond + 1 - 1.0e-5f);
				if (bWait) {
                    Thread.sleep(nMilliSecond);
                    nMilliSecond = 0;
                }
			}
		}catch(Exception e){
			Log.d(TAG, "PlayWave:" + e.getMessage());
			nMilliSecond = 0;
		}
		return (nMilliSecond + 99) / 100;
	}
	/** to load wave binary data by name and ContentResolver
	 * 
	 * @param cr ContentResolver typical from Activity
	 * @param strName name of wave stroke
	 * @return binary wave data comes from Content Provider
	 */
	protected static byte[] loadStrokeWave(ContentResolver cr, String strName) {
		Uri uri = null;
		int nID = 0;
		Cursor c = null;
		try{
			uri = Uri.parse(STROKEPY_CP);
			String strQuery = "strokename='" + strName + "'";
	        c = cr.query(uri, new String[]{"_ID"}, strQuery, null, null);
	        if (c == null)
	        	return null;
	        if (!c.moveToFirst() || c.getCount() != 1) {
	        	c.close();
	        	return null;
	        }
	        nID = c.getInt(0);
		}catch(Exception e){
			e.printStackTrace();
	        c.close();
			return null;
		}
        c.close();
        return loadFileContent(cr, uri, nID);
	}
	/** to load binary BYTEs from Content Provider by given URI and ID
	 * 
	 * @param cr content resolver, typical from Activity
	 * @param uri URI of Content Provider
	 * @param nID ID of row
	 * @return binary BYTEs from content provider
	 */
	protected static byte[] loadFileContent(ContentResolver cr, Uri uri, int nID) {
		if (uri == null) {
			uri = Uri.parse(STROKEPY_CP);
			if (uri == null)
				return null;
		}
        Uri uri2 = Uri.withAppendedPath(uri, "" + nID);
        ParcelFileDescriptor pfd;
        byte[] bData = null;
		try {
			pfd = cr.openFileDescriptor(uri2, "r");
	        if (pfd == null)
	        	return null;
        	FileDescriptor fd = pfd.getFileDescriptor();
        	if (fd != null)
        		bData = loadBytesFromFileDescriptor(fd);
        	// will delete file here.
        	pfd.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			bData = null;
			e.printStackTrace();
		}
		return bData;
	}
	/** to load binary from FileDescriptor
	 * 
	 * @param fd given FileDescriptor
	 * @return binary BYTEs of given file
	 */
	protected static byte[] loadBytesFromFileDescriptor(FileDescriptor fd) {
        byte[] bData = null;
		try {
    		FileInputStream fis = new FileInputStream(fd);
    		// for local file, available is trusted.
    		int nBytes = fis.available();
    		if (nBytes <= 0) {
    			fis.close();
    			return null;
    		}
    		bData = new byte[nBytes];
    		nBytes = fis.read(bData);
    		fis.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return bData;
	}
	/** to play wave by given .wav binary
	 * 
	 * @param bData binary .wav data, will be parsed by class WaveParser
	 * @return .T., if succeed; .F., if fail.
	 */
	protected boolean PlayData(byte[] bData) {
		if (mOutTrack == null) {
			int nBufSize = android.media.AudioTrack.getMinBufferSize(44100,
		               AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
			mOutBufSize = Math.max(3 * nBufSize, 10240);
		    mOutTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO,
		    		AudioFormat.ENCODING_PCM_16BIT, mOutBufSize, AudioTrack.MODE_STREAM);
		}
	    if (mOutTrack == null)
	    	return false;
	    mOutTrack.stop();
		int offset = WaveParser.getSkipBytes(bData);	// 44 BYTEs is wave head
		boolean bStart = true;
		// can not use << & +, it may lead nMax to less than 0
		int nMax = bData.length;
		if (offset > 4) {
			int nMax1 = WaveParser.getInt(bData, offset-4);
			nMax = Math.min(nMax1 + offset, nMax);
		}
		int nWrite = 0;
		while(offset < nMax) {
			int ret = mOutTrack.write( bData, offset, Math.min(nMax - offset, mOutBufSize) );
    		if (ret == AudioTrack.ERROR_INVALID_OPERATION)
    			return false;
    		if (ret != AudioTrack.ERROR_BAD_VALUE ) {
				offset += ret;
				nWrite += ret;
    		}
 			if (nWrite>=mOutBufSize && bStart) {
 				mOutTrack.play();
				bStart = false;
			}
    	}
    	// will this happen? wave duration shorter than bufSize
    	if (nWrite < mOutBufSize) {
    		int n = mOutBufSize - nWrite;
    		byte[] b = new byte[n];
    		for(int i=0; i<n; i++)
    			b[i] = 0;
    		mOutTrack.write(b, 0, n);
    	}
		if (bStart) {
			mOutTrack.play();
			bStart = false;
		}
		return true;
	}
	/** to release resources, free memory
	 */
	public void closeWave() {
		if (mOutTrack != null) {
			mOutTrack.stop();
			mOutTrack.release();
			mOutTrack = null;
		}
	}
}
